﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Collections.ObjectModel;
using System.Web.Compilation;
using System.Configuration;

namespace Casimeda.Framework.Dependency
{
    /// <summary>
    /// Inversion Of Controll Class controller
    /// </summary>
    public static class DependencyContainerController
    {
        static bool started;

        /// <summary>
        /// Gets the engine.
        /// </summary>
        public static IDependencyContainer Container { get; private set; }

        public static void SetCurrent(IDependencyContainer container) {
            if (Container != null) {
                Container.Shutdown();
            }

            Container = container;
        }

        /// <summary>
        /// Starts up.
        /// </summary>
        public static void StartUp() {
            if (started) {
                return;
            }

            try {
                ReadOnlyCollection<Assembly> dependencies = null;
                if (!IsHostedInAspnet())
                    dependencies = new ReadOnlyCollection<Assembly>(LoadAssembliesFromPath(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)).Where(x => x.FullName.StartsWith("Casimeda.")).ToList());
                else
                    dependencies = new ReadOnlyCollection<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>().Where(x => x.FullName.StartsWith("Casimeda.")).ToList());

                Type containerType = dependencies.SelectMany(x => x.GetTypes()).Single(t => !t.IsInterface && !t.IsAbstract && typeof(IDependencyContainerFactory).IsAssignableFrom(t));

                var factory = Activator.CreateInstance(containerType) as IDependencyContainerFactory;

                if (factory == null) {
                    throw new ConfigurationErrorsException(string.Format("The type {0} does not implement the IDependencyContainerFactory interface.", containerType.FullName));
                }

                Container = factory.Create();
                Container.Register("IDependencyContainer", typeof(IDependencyContainer), Container, DependencyLifeCycle.Singleton);
                Container.Register("IDependencyContainerRegister", typeof(IDependencyContainerRegister), Container, DependencyLifeCycle.Singleton);
                Container.Register("IDependencyContainerResolver", typeof(IDependencyContainerResolver), Container, DependencyLifeCycle.Singleton);

                var installer = factory.CreateInstaller();

                var coreInstaller = installer.GetInstallers(dependencies);
                coreInstaller.ForEach(x => x.ServiceRegistration(Container));
                coreInstaller.ForEach(x => x.ServiceRegistrationComplete(Container));
                var isInstalled = Container.Resolve<ISetupControl>().IsInstalled;

                if (isInstalled) {
                    coreInstaller.ForEach(x => x.ApplicationStarted(Container));
                }

                started = true;
            } catch (Exception e) {
                if (Container == null)
                    return;

                Container.Shutdown();
            }
        }

        static IEnumerable<Assembly> LoadAssembliesFromPath(string path) {
            var dlls = Directory.GetFiles(path, "*.dll");
            return dlls.Select(asm => Assembly.Load(Path.GetFileNameWithoutExtension(asm))).ToList();
        }

        /// <summary>
        /// Shutdowns this instance.
        /// </summary>
        public static void Shutdown() {
            Container.Shutdown();
        }

        /// <summary>
        /// Configures the specified config.
        /// </summary>
        /// <param name = "config">The config.</param>
        public static void Configure(string config) {
            Container.Configure(config);
        }

        /// <summary>
        /// Resolves the specified type.
        /// </summary>
        /// <param name = "type">The type.</param>
        /// <returns></returns>
        public static object Resolve(Type type) {
            if (type == null) {
                throw new ArgumentNullException();
            }

            return Container.Resolve(type);
        }

        /// <summary>
        /// Resolves the specified type.
        /// </summary>
        /// <param name = "type">The type.</param>
        /// <param name = "key">The key.</param>
        /// <returns></returns>
        public static object Resolve(Type type, string key) {
            if (type == null) {
                throw new ArgumentNullException();
            }

            if (key == null) {
                throw new ArgumentNullException();
            }

            if (key == string.Empty) {
                throw new ArgumentException();
            }

            return Container.Resolve(type, key);
        }

        /// <summary>
        /// Resolves this instance.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <returns></returns>
        public static T Resolve<T>() {
            return Container.Resolve<T>();
        }

        /// <summary>
        /// Resolves the specified key.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "key">The key.</param>
        /// <returns></returns>
        public static T Resolve<T>(string key) {
            if (key == null) {
                throw new ArgumentNullException();
            }

            if (key == string.Empty) {
                throw new ArgumentException();
            }

            return Container.Resolve<T>();
        }

        /// <summary>
        /// Registers the specified key.
        /// </summary>
        /// <param name = "key">The key.</param>
        /// <param name = "type">The type.</param>
        /// <param name = "implementedBy"></param>
        /// <param name = "lifeCycle">The life cycle.</param>
        public static void Register(string key, Type type, Type implementedBy, DependencyLifeCycle lifeCycle) {
            if (key == null) {
                throw new ArgumentNullException();
            }

            if (key == string.Empty) {
                throw new ArgumentException();
            }

            if (type == null) {
                throw new ArgumentNullException();
            }

            if (implementedBy == null) {
                throw new ArgumentNullException();
            }

            Container.Register(key, type, implementedBy, lifeCycle);
        }

        /// <summary>
        /// Registers the specified key.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <typeparam name = "TK"></typeparam>
        /// <param name = "key">The key.</param>
        /// <param name = "lifeCycle">The life cycle.</param>
        public static void Register<T, TK>(string key, DependencyLifeCycle lifeCycle) where TK : class , T {
            if (key == null) {
                throw new ArgumentNullException();
            }

            if (key == string.Empty) {
                throw new ArgumentException();
            }

            Container.Register<T, TK>(key, lifeCycle);
        }

        /// <summary>
        /// Registers the components by base class.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "assembly">The assembly.</param>
        /// <param name = "lifeCycle">The life cycle.</param>
        public static void RegisterComponentsByBaseClass<T>(Assembly assembly, DependencyLifeCycle lifeCycle) {
            if (assembly == null) {
                throw new ArgumentNullException();
            }

            Container.RegisterComponentsByBaseClass<T>(assembly, lifeCycle);
        }

        /// <summary>
        /// Resolves all.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <returns></returns>
        public static T[] ResolveAll<T>() {
            return Container.ResolveAll<T>();
        }

        /// <summary>
        /// Resolves all.
        /// </summary>
        /// <param name = "type">The type.</param>
        /// <returns></returns>
        public static object[] ResolveAll(Type type) {
            if (type == null) {
                throw new ArgumentNullException();
            }

            return Container.ResolveAll(type);
        }

        static bool IsHostedInAspnet() {
            return (AppDomain.CurrentDomain.GetData(".appDomain") != null);
        }
    }
}
